<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>沉浸式 Markdown 编辑器 | Electron × 鸿蒙</title>
    <!--
      页面说明：
      - 单面板沉浸式 WYSIWYG 编辑器，移除预览窗口、模式切换器与状态栏等干扰元素。
      - 使用 contenteditable 实现自然输入，实时将 Markdown 语法渲染为可视内容（标题、列表、引用、代码块、加粗/斜体等）。
      - 不依赖第三方库，适配鸿蒙容器的上下文隔离与安全要求，渲染过程进行 HTML 转义，禁止脚本注入。
    -->
    <style>
      :root { color-scheme: light dark; }
      body {
        margin: 0;
        font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
        background: #0f172a;
        color: #e5e7eb;
        display: grid;
        place-items: center;
        min-height: 100vh;
      }
      .card {
        width: min(880px, 96vw);
        background: rgba(255,255,255,0.06);
        backdrop-filter: blur(6px);
        border-radius: 16px;
        padding: 24px 28px;
        box-shadow: 0 10px 30px rgba(0,0,0,0.35);
        border: 1px solid rgba(255,255,255,0.12);
      }
      /* 沉浸式编辑器样式 */
      #wysi-editor {
        min-height: 60vh;
        max-height: 75vh;
        overflow: auto;
        border-radius: 10px;
        border: 1px solid rgba(255,255,255,0.18);
        background: rgba(255,255,255,0.06);
        padding: 14px;
        line-height: 1.7;
        -webkit-user-modify: read-write;
        outline: none;
      }
      #wysi-editor:empty:before {
        content: attr(data-placeholder);
        color: #94a3b8;
      }
      #wysi-editor h1, #wysi-editor h2, #wysi-editor h3 { margin: 10px 0; }
      #wysi-editor pre {
        background: rgba(255,255,255,0.08);
        border-radius: 8px;
        padding: 10px;
        overflow: auto;
      }
      #wysi-editor blockquote {
        margin: 8px 0;
        padding: 6px 8px;
        border-left: 3px solid #93c5fd;
        background: rgba(255,255,255,0.06);
      }
    </style>
  </head>
  <body>
    <main class="card" aria-label="沉浸式 Markdown 编辑器">
      <!-- 单面板编辑器：实时渲染 Markdown 为可视内容，隐藏语法符号与预览窗口 -->
      <div id="wysi-editor" contenteditable="true" data-placeholder="开始创作，支持标题、列表、引用、代码块、加粗/斜体等"></div>
    </main>

    <script>
      // 初始化沉浸式 WYSIWYG 编辑器
      document.addEventListener('DOMContentLoaded', () => {
        const editor = document.getElementById('wysi-editor');
        if (!editor) return;
        // 光标保持与渲染控制变量
        const CARET_MARK = '\uE000'; // 私有区字符作为光标标记
        let composing = false;        // 中文等合成输入保护
        let isRendering = false;      // 渲染状态，避免递归 input
        let lastCaretOffset = 0;      // 最近光标偏移，用于稳定恢复
        // 使用私用区字符作为光标标记，避免与用户文本冲突
       

        // ---------- Markdown 渲染器（块级/行内规则、HTML 安全转义） ----------
        const escapeHtml = (s) => String(s)
          .replace(/&/g, '&amp;')
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;');

        const applyInline = (text) => {
          // 加粗 **text**
          text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
          // 斜体 *text*
          text = text.replace(/(^|\W)\*(.+?)\*(?=\W|$)/g, '$1<em>$2</em>');
          // 行内代码 `code`
          text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
          // 链接 [text](https://...)
          text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
          return text;
        };

        const renderMarkdown = (md) => {
          let src = escapeHtml(md.replace(/\r\n/g, '\n'));
          // 代码块 ``` ... ```
          src = src.replace(/```([\s\S]*?)```/g, (m, code) => {
            return `<pre><code>${code}</code></pre>`;
          });
          const lines = src.split('\n');
          let html = '';
          let inUl = false, inOl = false;
          const closeLists = () => { if (inUl) { html += '</ul>'; inUl = false; } if (inOl) { html += '</ol>'; inOl = false; } };
          for (const line of lines) {
            if (/^\s*(?:-{3,}|_{3,}|\*{3,})\s*$/.test(line)) { closeLists(); html += '<hr />'; continue; }
            const hMatch = line.match(/^(#{1,6})\s+(.*)$/);
            if (hMatch) { closeLists(); const level = hMatch[1].length; html += `<h${level}>${applyInline(hMatch[2])}</h${level}>`; continue; }
            const bqMatch = line.match(/^>\s+(.*)$/);
            if (bqMatch) { closeLists(); html += `<blockquote>${applyInline(bqMatch[1])}</blockquote>`; continue; }
            let m;
            if ((m = line.match(/^\s*[-*]\s+(.*)$/))) { if (!inUl) { closeLists(); html += '<ul>'; inUl = true; } html += `<li>${applyInline(m[1])}</li>`; continue; }
            if ((m = line.match(/^\s*\d+\.\s+(.*)$/))) { if (!inOl) { closeLists(); html += '<ol>'; inOl = true; } html += `<li>${applyInline(m[1])}</li>`; continue; }
            if (line.trim() === '') { closeLists(); html += '<br />'; continue; }
            closeLists(); html += `<p>${applyInline(line)}</p>`;
          }
          closeLists();
          return html;
        };

        // ---------- 选择与光标工具（在 contenteditable 中保持光标位置） ----------
        const getTextNodes = (root) => {
          const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
          const nodes = []; let node;
          while ((node = walker.nextNode())) nodes.push(node);
          return nodes;
        };
        const getCaretOffset = (root) => {
          const sel = window.getSelection();
          if (!sel || sel.rangeCount === 0) return 0;
          const range = sel.getRangeAt(0);
          const nodes = getTextNodes(root);
          let offset = 0;
          for (const n of nodes) {
            if (n === range.startContainer) { offset += range.startOffset; break; }
            offset += n.textContent.length;
          }
          return offset;
        };
        const setCaretOffset = (root, target) => {
          const nodes = getTextNodes(root);
          let remain = Math.max(0, target);
          for (const n of nodes) {
            const len = n.textContent.length;
            if (remain <= len) {
              const sel = window.getSelection();
              const range = document.createRange();
              range.setStart(n, remain);
              range.collapse(true);
              sel.removeAllRanges(); sel.addRange(range);
              return;
            }
            remain -= len;
          }
          const last = nodes[nodes.length - 1];
          if (last) { const sel = window.getSelection(); const range = document.createRange(); range.setStart(last, last.textContent.length); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); }
        };
        const getPlainText = () => editor.innerText || '';

        // 查找并定位到光标标记位置，然后清除标记字符
        const placeCaretAtMarker = (root) => {
          const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
          let node;
          while ((node = walker.nextNode())) {
            const idx = node.textContent.indexOf(CARET_MARK);
            if (idx !== -1) {
              const sel = window.getSelection();
              const range = document.createRange();
              // 光标置于标记字符之前
              range.setStart(node, idx);
              range.collapse(true);
              sel.removeAllRanges(); sel.addRange(range);
              // 移除标记字符
              node.textContent = node.textContent.replace(CARET_MARK, '');
              return true;
            }
          }
          return false;
        };

        // 在 contenteditable 中插入纯文本（用于缩进与前缀续写）
        const insertPlainText = (text) => {
          const sel = window.getSelection();
          if (!sel || sel.rangeCount === 0) return;
          const range = sel.getRangeAt(0);
          range.deleteContents();
          const node = document.createTextNode(text);
          range.insertNode(node);
          const newRange = document.createRange();
          newRange.setStart(node, node.textContent.length);
          newRange.collapse(true);
          sel.removeAllRanges(); sel.addRange(newRange);
        };

        // 回车：自动缩进 + 列表/引用前缀延续
        const handleEnter = (e) => {
          if (e.key !== 'Enter' || e.isComposing) return;
          e.preventDefault();
          const plain = getPlainText();
          const caret = getCaretOffset(editor);
          const lines = plain.split('\n');
          let acc = 0, idx = 0;
          for (; idx < lines.length; idx++) { const nextAcc = acc + lines[idx].length + 1; if (caret <= nextAcc) break; acc = nextAcc; }
          const lineText = lines[idx] || '';
          const indentMatch = lineText.match(/^(\s+)/);
          const indent = indentMatch ? indentMatch[1] : '';
          let prefix = '';
          if (/^\s*>\s+/.test(lineText)) prefix = '> ';
          else if (/^\s*[-*]\s+/.test(lineText)) prefix = lineText.match(/^\s*([-*])\s+/)[1] + ' ';
          else if (/^\s*\d+\.\s+/.test(lineText)) { const m = lineText.match(/^\s*(\d+)\.\s+/); prefix = (m ? m[1] : '1') + '. '; }
          insertPlainText('\n' + indent + prefix);
          // 更新最近光标位置并渲染
          lastCaretOffset = getCaretOffset(editor);
          scheduleRender();
        };

        // Tab：缩进（简化版，反缩进与多行缩进可后续增强）
        const INDENT = '  ';
        const handleTab = (e) => {
          if (e.key === 'Tab' && !e.isComposing) {
            e.preventDefault();
            insertPlainText(INDENT);
            lastCaretOffset = getCaretOffset(editor);
            scheduleRender();
          }
        };

        editor.addEventListener('keydown', (e) => { if (e.key === 'Enter') return handleEnter(e); if (e.key === 'Tab') return handleTab(e); });

        // 输入 → 实时可视化渲染（WYSIWYG），采用光标标记法保持位置
        let renderTimer = null;
        const renderEditor = () => {
          if (isRendering) return;
          isRendering = true;
          try {
            const plain = getPlainText();
            const prevScrollTop = editor.scrollTop; // 记录滚动，避免渲染导致跳动
            // 使用最近记录的光标偏移插入标记，避免因选择丢失导致跳到开头
            const caret = lastCaretOffset;
            const withMarker = plain.slice(0, caret) + CARET_MARK + plain.slice(caret);
            editor.innerHTML = renderMarkdown(withMarker);
            // 保持焦点，防止重建 DOM 后丢失选择
            editor.focus({ preventScroll: true });
            // 将光标恢复到标记处，并清除标记字符
            if (!placeCaretAtMarker(editor)) {
              setCaretOffset(editor, caret);
            }
            // 恢复渲染前的滚动位置
            editor.scrollTop = prevScrollTop;
          } finally {
            isRendering = false;
          }
        };
        const scheduleRender = () => {
          if (composing || isRendering) return; // 合成输入中或渲染中不重复调度
          if (renderTimer) clearTimeout(renderTimer);
          renderTimer = setTimeout(renderEditor, 40);
        };
        editor.addEventListener('beforeinput', () => { lastCaretOffset = getCaretOffset(editor); });
        editor.addEventListener('input', () => { if (isRendering || composing) return; lastCaretOffset = getCaretOffset(editor); scheduleRender(); });
        // 监听选择变化：用户用方向键/鼠标移动光标时，实时记录偏移
        document.addEventListener('selectionchange', () => {
          if (document.activeElement === editor && !isRendering) {
            lastCaretOffset = getCaretOffset(editor);
          }
        });
        editor.addEventListener('compositionstart', () => { composing = true; });
        editor.addEventListener('compositionend', () => { composing = false; lastCaretOffset = getCaretOffset(editor); scheduleRender(); });

        // 粘贴为纯文本，避免外部样式与脚本注入
        editor.addEventListener('paste', (e) => {
          try {
            e.preventDefault();
            const text = e.clipboardData?.getData('text/plain') || '';
            insertPlainText(text);
            lastCaretOffset = getCaretOffset(editor);
            scheduleRender();
          } catch (_) {}
        });

        // 初始化一次渲染
        lastCaretOffset = 0;
        renderEditor();
      });
    </script>
  </body>
  </html>